package cn.richx.pushmessage.service;

import cn.richx.pushmessage.config.ApplicationProperties;
import cn.richx.pushmessage.domain.PushMessage;
import cn.richx.pushmessage.domain.UserDTO;
import cn.richx.pushmessage.message.PushItem;
import cn.richx.pushmessage.repository.PushMessageRepository;
import cn.richx.pushmessage.rpc.feign.UserService;
import com.baidu.yun.core.log.YunLogEvent;
import com.baidu.yun.push.auth.PushKeyPair;
import com.baidu.yun.push.client.BaiduPushClient;
import com.baidu.yun.push.constants.BaiduPushConstants;
import com.baidu.yun.push.exception.PushClientException;
import com.baidu.yun.push.exception.PushServerException;
import com.baidu.yun.push.model.PushMsgToSingleDeviceRequest;
import com.baidu.yun.push.model.PushMsgToSingleDeviceResponse;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.Executor;

/**
 * @author caohao
 * @date 2018-04-23
 */
@Service
@Transactional
public class PushMessageService {

    private static Logger logger = LoggerFactory.getLogger(PushMessageService.class);


    private final Executor executor;

    private final ApplicationProperties applicationProperties;

    private final UserService userService;

    private final PushMessageRepository pushMessageRepository;

    public PushMessageService(Executor executor, ApplicationProperties applicationProperties, UserService userService, PushMessageRepository pushMessageRepository) {
        this.executor = executor;
        this.applicationProperties = applicationProperties;
        this.userService = userService;
        this.pushMessageRepository = pushMessageRepository;
    }

    /**
     * Save a pushMessage.
     *
     * @param pushMessage the entity to save
     * @return the persisted entity
     */
    public PushMessage save(PushMessage pushMessage) {
        logger.debug("Request to save PushMessage : {}", pushMessage);
        return pushMessageRepository.save(pushMessage);
    }

    /**
     * Get all the pushMessages.
     *
     * @param pageable the pagination information
     * @return the list of entities
     */
    @Transactional(readOnly = true)
    public Page<PushMessage> findAll(Pageable pageable) {
        logger.debug("Request to get all PushMessages");
        return pushMessageRepository.findAll(pageable);
    }

    /**
     * Get one pushMessage by id.
     *
     * @param id the id of the entity
     * @return the entity
     */
    @Transactional(readOnly = true)
    public PushMessage findOne(Long id) {
        logger.debug("Request to get PushMessage : {}", id);
        return pushMessageRepository.findOne(id);
    }

    /**
     * Delete the pushMessage by id.
     *
     * @param id the id of the entity
     */
    public void delete(Long id) {
        logger.debug("Request to delete PushMessage : {}", id);
        pushMessageRepository.delete(id);
    }

    public void push(PushItem pushItem, Map<String, Object> resp) {
        try {
            push(pushItem);
        } catch (Exception e) {
            resp.put("ResponseResult", "推送失败");
            resp.put("ResponseObject", -1);
            e.printStackTrace();
        }
    }

    public void push(PushItem pushItem) {
        new AsyncPushExecutor(Arrays.asList(pushItem)).run();
    }

    public void push(List<PushItem> pushItemList) {
        new AsyncPushExecutor(pushItemList).run();
    }

    public void asyncPush(PushItem pushItem) {
        executor.execute(new AsyncPushExecutor(Arrays.asList(pushItem)));
    }

    public void asyncPush(List<PushItem> pushItemList) {
        executor.execute(new AsyncPushExecutor(pushItemList));
    }

    public void asyncPushOnlyId(List<PushItem> pushItemList, String baseId) {
        executor.execute(new AsyncPushExecutor(pushItemList, true, baseId));
    }

    /**
     * 异步推送消息内部类
     * */
    private class AsyncPushExecutor implements Runnable {

        private List<PushItem> pushItemList;
        private PushItem pushItem;
        private int type;

        @Override
        public void run() {
            logger.info("开始异步推送...");
            if(type == 1) {
                pushItemList(pushItemList);
            } else if(type == 2) {
                pushItemList(Arrays.asList(pushItem));
            }
        }

        AsyncPushExecutor(List<PushItem> pushItemList) {
            this.pushItemList = pushItemList;
            this.type = 1;
        }

        AsyncPushExecutor(PushItem pushItem) {
            this.pushItem = pushItem;
            this.type = 2;
        }

        AsyncPushExecutor(List<PushItem> pushItemList, boolean onlyId, String baseId) {
            for(PushItem pushItem : pushItemList) {
                UserDTO user = userService.getUserById((long) pushItem.getReceiverId(), baseId).getResponseObject();
                pushItem.setChannels(user.getBaiduChannelId());
                pushItem.setDeviceType(String.valueOf(user.getDeviceType()));
            }
            this.pushItemList = pushItemList;
            this.type = 1;
        }

        public List<PushItem> getPushItemList() {
            return pushItemList;
        }

        public void setPushItemList(List<PushItem> pushItemList) {
            this.pushItemList = pushItemList;
        }

        public PushItem getPushItem() {
            return pushItem;
        }

        public void setPushItem(PushItem pushItem) {
            this.pushItem = pushItem;
        }

        public int getType() {
            return type;
        }

        public void setType(int type) {
            this.type = type;
        }


        private void pushItemList(List<PushItem> pushItemList) {
            for(PushItem pushItem : pushItemList) {
                try {
                    if(StringUtils.isNotBlank(pushItem.getDeviceType()) && StringUtils.isNotBlank(pushItem.getChannels())) {
                        pushItem.setPushContent(StringUtils.isEmpty(pushItem.getPushContent()) ? applicationProperties.getName() + "向您发送了一条新消息，请注意查收" : pushItem.getPushContent());
                        PushMessage message = new PushMessage(pushItem);
                        message.setStatus(0);
                        message.setCreateTime(new Date().getTime() + "");
                        pushMessageRepository.save(message);
                        pushItem.setPushId(message.getPushId().intValue());
                        pushItem.setCreateTime(message.getCreateTime());

                        switch(pushItem.getDeviceType()) {
                            case "3" :
                                pushAndroid(pushItem);
                                break;
                            case "4":
                                int badge = Math.toIntExact(pushMessageRepository.countByStatus(pushItem.getReceiverId()));
                                pushIOS(pushItem, badge);
                                break;
                        }
                    } else {
                        PushMessage message = new PushMessage(pushItem);
                        message.setStatus(1);
                        pushMessageRepository.save(message);
                    }
                } catch (Exception e) {
                    if(StringUtils.isNotBlank(pushItem.getPushId() + "")) {
                        // 推送报错情况下，更新推送之前保存的pushmessage
                        PushMessage message = new PushMessage(pushItem);
                        message.setStatus(1);
                        pushMessageRepository.save(message);
                    }
                    e.printStackTrace();
                }
            }
        }

        /**
         * 安卓推送单个设备
         * */
        private void pushAndroid(PushItem pushItem) throws PushClientException, PushServerException {
            ApplicationProperties.Push push = applicationProperties.getPush();
            PushKeyPair pair = new PushKeyPair(push.getApiKey(), push.getSecretKey());
            BaiduPushClient pushClient = new BaiduPushClient(pair, BaiduPushConstants.CHANNEL_REST_URL);
            pushClient.setChannelLogHandler((YunLogEvent event) -> logger.debug(event.getMessage()));

            Map<String, Object> msg = pushItem.convertMsgMap(applicationProperties.getName());

            PushMsgToSingleDeviceRequest request = new PushMsgToSingleDeviceRequest()
                .addChannelId(pushItem.getChannels())
                .addMsgExpires(applicationProperties.getPush().getMessageSurvivalTime()) // message有效时间
                .addMessageType(0)// 1：通知,0:透传消息. 默认为0 注：IOS只有通知.
                .addMessage(JSONObject.fromObject(msg).toString()).addDeviceType(3);

            try {
                PushMsgToSingleDeviceResponse response = pushClient.pushMsgToSingleDevice(request);
            } catch (PushClientException e) {
                if(BaiduPushConstants.ERROROPTTYPE) {
                    throw e;
                } else {
                    logger.error("亦信，android，PushClientException推送错误："+"reciverId：" + pushItem
                        .getReceiverId() + ", "+"deviceType：" + pushItem.getDeviceType() + ", "
                        + String.format("requestId: %d, errorCode: %d, errorMessage: %s")+ e.getMessage());
                }
            } catch (PushServerException e) {
                if(BaiduPushConstants.ERROROPTTYPE) {
                    throw e;
                } else {
                    logger.error("亦信，android，PushServerException推送错误："+"reciverId：" + pushItem.getReceiverId()
                        + ", " + " deviceType：" + pushItem.getDeviceType() + ", " +
                        String.format("requestId: %d, errorCode: %d, errorMessage: %s", e.getRequestId(), e.getErrorCode(), e.getErrorMsg()));
                }
            }
        }

        /**
         * IOS 推送单个设备
         * */
        private void pushIOS(PushItem pushItem, int badge) throws PushClientException, PushServerException {
            ApplicationProperties.Push push = applicationProperties.getPush();
            PushKeyPair pair = new PushKeyPair(push.getIosApiKey(), push.getIosSecretKey());
            BaiduPushClient pushClient = new BaiduPushClient(pair, BaiduPushConstants.CHANNEL_REST_URL);
            pushClient.setChannelLogHandler((YunLogEvent event) -> logger.debug(event.getMessage()));

            HashMap<String, Object> aps = new HashMap<String, Object>();
            pushItem.setAps(aps);
            aps.put("sound", "default");
            aps.put("badge", badge);
            aps.put("alert", StringUtils.isEmpty(pushItem.getPushContent()) ? applicationProperties.getName() + "向您发送了一条新消息，请注意查收" : pushItem.getPushContent());

            PushMsgToSingleDeviceRequest request = new PushMsgToSingleDeviceRequest()
                .addMessage(JSONObject.fromObject(pushItem).toString())
                .addChannelId(pushItem.getChannels())
                .addMsgExpires(applicationProperties.getPush().getMessageSurvivalTime())
                .addMessageType(1)
                .addDeviceType(4)
                .addDeployStatus(applicationProperties.getPush().getEnvironment()); // 1. 开发环境 2. 生产环境

            try {
                PushMsgToSingleDeviceResponse response = pushClient.pushMsgToSingleDevice(request);
                logger.debug("msgId: " + response.getMsgId() + ",sendTime: " + response.getSendTime());
            } catch (PushClientException e) {
                if (BaiduPushConstants.ERROROPTTYPE) {
                    throw e;
                } else {
                    logger.error("亦信，ios，PushClientException推送错误："+"reciverId：" + pushItem
                        .getReceiverId() + ", "+"deviceType：" + pushItem.getDeviceType() + ", "
                        + String.format("错误信息: ") + e.getMessage());
                }
            } catch (PushServerException e) {
                if(BaiduPushConstants.ERROROPTTYPE) {
                    throw e;
                } else {
                    logger.error("亦信，ios，PushServerException推送错误："+"reciverId：" + pushItem.getReceiverId()
                        + ", " + " deviceType：" + pushItem.getDeviceType() + ", " +
                        String.format("requestId: %d, errorCode: %d, errorMessage: %s", e.getRequestId(), e.getErrorCode(), e.getErrorMsg()));
                }
            }
        }
    }

    @Transactional(readOnly = true)
    public List<PushMessage> getNoReadList(UserDTO user, Long lastId) {
        return pushMessageRepository.getNoReadList(lastId, String.valueOf(user.getId()));
    }
}
